from cProfile import Profile
from eden.evo.aminos import Amino
from eden.evo.context import Context
from eden.evo.evaluator import SimpleEvaluator
from eden.evo.pool import NonsexualPool
from eden.evo.translate import Translator
from optparse import OptionParser
import os
import random
import sys
import time

class PrintEvent:
    def __init__(self, interval):
        self.interval = interval
        
    def __call__(self, pool):
        if pool.generation % self.interval == 0:
            print 'Generation %d --> %f %f' % (pool.generation, pool.fitness, pool.hitratio)

class SaveEvent:
    def __init__(self, dir):
        self.dir = dir
        
    def __call__(self, pool):
        pool.save_to_file(self.dir)
        
class Problem(object):
    SAVE_DIR = os.path.join('..', '..', '..', 'result')
    
    def __init__(self, args, options):
        self.args = args
        self.options = options
        print 'running %s ...' % self.__class__.__name__
        print 'init ...'
        self.__clean_dir()
        self.__init_pool()
        
    @staticmethod
    def list_problems():
        problems = {}
        Problem.__list_sub_classes(Problem, problems)
        return problems
        
    @staticmethod
    def __list_sub_classes(cls, problems):
        subclasses = cls.__subclasses__()
        if len(subclasses) == 0: # is concrete class
            problems[cls.__name__] = cls
        else: # is abstract class
            for subclass in subclasses:
                Problem.__list_sub_classes(subclass, problems)

    def solve(self):
        print 'running ...'
        starttime = time.time()
        self.pool.evolve(self._test_data(), -1)
        endtime = time.time()
        ms_per_iter = (endtime - starttime) * 1000 / (self.pool.generation + 1)
        print 'milliseconds per iteration: %f' % ms_per_iter
        if self.options.savemode == 'last':
            self.__save_to_file()
        print('solved on generation %d' % self.pool.generation)
        
    def profile(self):
        prof = Profile().runctx('self.solve()', None, { 'self' : self })
        prof.print_stats('time')

    def show_solutions(self):
        solutions = self.__get_solutions()
        for (name, protein) in solutions:
            messages = self.__run_solution(protein)
            self.__print_solution(name, messages)
        
    def search_random_seed(self, seed_start, max_generations):
        self.options.verbose = False
        while True:
            print('Trying seed %d' % seed_start)
            random.seed(seed_start)
            self.__init_pool()
            self.__runpool(max_generations)
            if self.pool.getgeneration() < max_generations:
                print('Pool with random seed %d succeed on generation %d.' % (seed_start, self.pool.getgeneration()))
                return
            seed_start += 1

    def _get_aminos(self):
        return Amino.get_predefined_aminos()
    
    def _get_evaluator(self):
        return SimpleEvaluator()
        
    def __clean_dir(self):
        if os.path.exists(Problem.SAVE_DIR):
            for filename in os.listdir(Problem.SAVE_DIR):
                os.remove(os.path.join(Problem.SAVE_DIR, filename))

    def __init_pool(self):
        aminos = self._get_aminos()
        encoding = Translator.get_encoding(aminos)
        evaluator = self._get_evaluator()
        self.pool = NonsexualPool(100, 10, encoding, evaluator)
        if self.options.verbose:
            self.pool.add_event(PrintEvent(int(self.options.print_generation_interval)))
        if self.options.savemode == 'eachstep':
            self.pool.add_event(SaveEvent(Problem.SAVE_DIR))
            
    def __get_solutions(self):
        func_names = [name for name in dir(self.__class__) if name.startswith('solution')]
        functions = [getattr(self.__class__, name) for name in func_names]
        proteins = [(function.__name__, function(self)) for function in functions]
        return proteins
        
    def __run_solution(self, protein):
        messages = []
        for input, expected in self._test_data():
            context = Context.from_data(input)
            protein.run(context)
            output = context.getregisters()[:len(expected)]
            equation = self._format_solution_equation(input, expected)
            result = 'Correct' if output[0] == expected[0] else 'Incorrect'
            messages.append((equation, result))
        return messages
    
    def _format_solution_equation(self, input, output):
        pass

    def __print_solution(self, name, messages):
        print(name.upper())
        max_equation_len = max((len(equation) for (equation, result) in messages))
        for equation, result in messages:
            print '%s  %s' % (equation.ljust(max_equation_len), result)
        all_results = [result for (equation, result) in messages]
        print('Has errors!' if 'Incorrect' in all_results else 'All correct!')
        print('')

    def __save_to_file(self):
        print 'saving to file ...'
        self.pool.save_to_file(Problem.SAVE_DIR)

    def _test_data(self):
        pass

def generate_usage():
    problems = Problem.list_problems()
    problem_names = [problem_name for problem_name in problems.keys()]
    usage = 'problems.py [options] action problem\n\n'
    usage += 'Args:\n'
    usage += '  action  specifies the action to run, valid values are (solve, profile, show_solutions, search_random_seed)'
    usage += '  problem  specifies which problem to solve, valid values are '
    usage += '(' + ', '.join(problem_names) + ')'
    return usage

def parse_cmd():
    usage = generate_usage()
    parser = OptionParser(usage)
    parser.add_option('--print-generation-interval', dest='print_generation_interval', default='1', help='print info every n generations')
    parser.add_option('--savemode', dest='savemode', default='never', help='how to save results, valid values are (never, last, eachstep)')
    parser.add_option('-v', '--verbose', dest='verbose', default=False, action='store_true')
    options, args = parser.parse_args()
    if len(args) != 2:
        parser.print_help()
        sys.exit(1)
    return (options, args)

if __name__ == '__main__':
    from demo.evo.algebra import * #@UnusedWildImport
    from demo.evo.rock_paper_scissors import * #@UnusedWildImport
    random.seed(0)
    (options, args) = parse_cmd()
    action = args[0]
    problem_name = args[1]
    problems = Problem.list_problems()
    problem_class = problems[problem_name]
    problem = problem_class(args, options)
    function = getattr(problem, action)
    function()